import { KeycloakInstance } from "keycloak-js";
import { AuthorizationRequest } from "keycloak-js/dist/keycloak-authz";

export interface KeycloakPermission {
  scopes: string[];
  rsid: string;
  rsname: string;
}

export interface KeycloakJwt {
  authorization: {
    permissions: KeycloakPermission[];
  };
}

export class KeycloakAuthorization {
  keycloak: KeycloakInstance;
  rpt?: string;
  config?: { rpt_endpoint: string; token_endpoint: string };

  constructor(keycloak: KeycloakInstance) {
    this.keycloak = keycloak;
  }
  init(): Promise<void> {
    return new Promise((resolve, reject) => {
      let req = new XMLHttpRequest();
      req.open(
        "GET",
        `${this.keycloak.authServerUrl}/realms/${this.keycloak.realm}/.well-known/uma2-configuration`
      );
      req.onreadystatechange = () => {
        if (req.readyState === 4) {
          if (req.status === 200) {
            this.config = JSON.parse(req.responseText);
            resolve();
          } else {
            reject("Could not obtain configuration from server.");
          }
        }
      };
      req.send();
    });
  }

  /**
   * This method enables client applications to better integrate with resource servers protected by a Keycloak
   * policy enforcer using UMA protocol.
   *
   * The authorization request must be provided with a ticket.
   */
  authorize(authorizationRequest: AuthorizationRequest): Promise<string> {
    return new Promise<string>((resolve, reject) => {
      if (!authorizationRequest?.ticket) {
        reject("There is no ticket in the authorization request");
        return;
      }
      if (!this.config?.token_endpoint) {
        reject("There is no token_endpoint in the config");
        return;
      }

      let req = new XMLHttpRequest();
      req.open("POST", this.config.token_endpoint, true);
      req.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
      req.setRequestHeader("Authorization", "Bearer " + this.keycloak.token);
      req.onreadystatechange = () => {
        if (req.readyState === 4) {
          var status = req.status;

          if (status >= 200 && status < 300) {
            this.rpt = JSON.parse(req.responseText).access_token;
            if (this.rpt) resolve(this.rpt);
            else reject("Cannot get rpt from response");
          } else if (status == 403) {
            reject("Authorization request was denied by the server.");
          } else {
            reject("Could not obtain authorization data from server.");
          }
        }
      };

      let params = `grant_type=urn:ietf:params:oauth:grant-type:uma-ticket&client_id=${this.keycloak.clientId}&ticket=${authorizationRequest.ticket}`;
      if (authorizationRequest.submitRequest) {
        params += `&submit_request=${authorizationRequest.submitRequest}`;
      }

      let metadata = authorizationRequest.metadata;
      if (metadata) {
        if (metadata.responseIncludeResourceName) {
          params += `&response_include_resource_name=${metadata.responseIncludeResourceName}`;
        }
        // @ts-ignore
        if (metadata.responsePermissionsLimit) {
          // @ts-ignore
          params += `&response_permissions_limit=${metadata.responsePermissionsLimit}`;
        }
      }

      if (
        this.rpt &&
        (authorizationRequest.incrementalAuthorization === undefined ||
          authorizationRequest.incrementalAuthorization)
      ) {
        params += `&rpt=${this.rpt}`;
      }
      req.send(params);
    });
  }

  /**
   * Obtains all entitlements from a Keycloak Server based on a given resourceServerId.
   */
  entitlement(
    resourceServerId: string,
    authorizationRequest?: AuthorizationRequest
  ): Promise<string> {
    return new Promise<string>((resolve, reject) => {
      if (!this.config?.token_endpoint) {
        reject("There is no token_endpoint in the config");
        return;
      }
      let req = new XMLHttpRequest();
      req.open("POST", this.config.token_endpoint, true);
      req.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
      req.setRequestHeader("Authorization", "Bearer " + this.keycloak.token);
      req.onreadystatechange = () => {
        if (req.readyState === 4) {
          var status = req.status;
          if (status >= 200 && status < 300) {
            this.rpt = JSON.parse(req.responseText).access_token;
            if (this.rpt) resolve(this.rpt);
            else reject("Cannot get rpt from response");
          } else if (status === 403) {
            reject("Authorization request was denied by the server.");
          } else {
            reject("Could not obtain authorization data from server.");
          }
        }
      };

      if (!authorizationRequest) authorizationRequest = {};
      let params = `grant_type=urn:ietf:params:oauth:grant-type:uma-ticket&client_id=${this.keycloak.clientId}`;
      // @ts-ignore
      if (authorizationRequest.claimToken) {
        // @ts-ignore
        params += `&claim_token=${authorizationRequest.claimToken}`;
        // @ts-ignore
        if (authorizationRequest.claimTokenFormat) {
          // @ts-ignore
          params += `&claim_token_format=${authorizationRequest.claimTokenFormat}`;
        }
      }

      params += `&audience=${resourceServerId}`;
      authorizationRequest.permissions?.forEach((resource) => {
        var permission = resource.id;
        if (resource.scopes && resource.scopes.length > 0) {
          permission += "#";
          resource.scopes.forEach((scope) => {
            if (permission.indexOf("#") != permission.length - 1) {
              permission += ",";
            }
            permission += scope;
          });
        }
        params += "&permission=" + permission;
      });

      var metadata = authorizationRequest.metadata;
      if (metadata) {
        if (metadata.responseIncludeResourceName) {
          params += `&response_include_resource_name=${metadata.responseIncludeResourceName}`;
        }
        // @ts-ignore
        if (metadata.responsePermissionsLimit) {
          // @ts-ignore
          params += `&response_permissions_limit=${metadata.responsePermissionsLimit}`;
        }
      }
      if (this.rpt) {
        params += `&rpt=${this.rpt}`;
      }

      req.send(params);
    });
  }
}
